﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Collections;
using System.IO;
using GiMessCommons;
using System.Threading;

namespace GiMess.Net
{
    public class NetMsg
    {
        private string header  = "GMSG";    //4B
        private string version = "1000";    //8B
        private int    service;             //12B
        private int    pktLen;              //16B
        private string data;

        #region Constructors
        public NetMsg(NetServices Service)
        {
            this.service = (int)Service;
            pktLen = 0;
            data = "";
        }

        public NetMsg(NetServices Service, string Data)
        {
            this.service = (int)Service;
            data = Data;
            pktLen = data.Length;
        }
        #endregion

        #region Properties

        public string Header
        {
            get { return header; }
            //set { header = value; }
        }

        public string Version
        {
            get { return version; }
            set { version = value; }
        }

        public int Service
        {
            get { return service; }
            set { service = value; }
        }

        public int Length
        {
            get { return pktLen; }
            //set { pktLen = value; }
        }

        public string Data
        {
            get { return data; }
            set { data = value; pktLen = data.Length; }
        }

        #endregion
    }

    #region Exceptions

    [Serializable()]
    public class NetMsgNotValidExeption:Exception
    {
        public NetMsgNotValidExeption() : base() { }
        public NetMsgNotValidExeption(string message) : base(message) { }
        public NetMsgNotValidExeption(string message, Exception inner) : base(message, inner) { }

        protected NetMsgNotValidExeption(System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) { }

        public string Messsage { get; set; }
    }

    [Serializable()]
    public class NetMsgNotCompleteExeption : Exception
    {
        public NetMsgNotCompleteExeption() : base() { }
        public NetMsgNotCompleteExeption(string message) : base(message) { }
        public NetMsgNotCompleteExeption(string message, Exception inner) : base(message, inner) { }

        protected NetMsgNotCompleteExeption(System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) { }

        public string Messsage { get; set; }
    }

    #endregion

    public delegate void NewNetMsgEventHandler(NetMsg msg);

    public class NetClient
    {
        private TcpClient       client;
        private NetworkStream   netStream;
        private StreamWriter    netStreamWriter;
        private StreamReader    netStreamReader;
        private Thread          clientThread;
        //private BinaryReader  netStreamBinary;

        //private Encoding decoder;// = NetConsants.StringEncoder;

        private string stringBuffer = "";
        private int    tryRead;
        private char[] charBuffer;
        private byte[] byteBuffer;
        private NetMsg msgRecv;

        public  event  NewNetMsgEventHandler NewNetMsg;

        //private StringBuilder sb;

        // DEPRECATED
        //private ArrayList buffer; 
        //private byte[] byteBuffer;

        public Thread WorkerThread
        {
            get { return clientThread; }
        }

        public NetClient()
        {
            client  = new TcpClient();
            //decoder = Encoding.GetEncoding(NetConsants.StringEncoder);
            //sb = new StringBuilder();
            //buffer = new ArrayList();
        }

        public bool Connect(string host, int port)
        {
            try
            {
                client.Connect(host, port);
                
                netStream = client.GetStream();
                netStream.ReadTimeout = 10;
                netStreamWriter = new StreamWriter(netStream);
                netStreamWriter.AutoFlush = false;

                netStreamReader = new StreamReader(netStream);

                clientThread = new Thread(new ThreadStart(Listen));
                clientThread.Name = "NetClientWorker";
                clientThread.Start();

                return true;
            }
            catch
            {
                return false;
            }
        }

        public void Disconnect()
        {
            client.Close();
        }

        public bool SendNetMsg(NetMsg msg)
        {
            netStreamWriter.Write( msg.Header  );
            netStreamWriter.Write( msg.Version );
            netStreamWriter.Write( NetUtils.intToString(msg.Service));
            netStreamWriter.Write( NetUtils.intToString(msg.Length  ));
            netStreamWriter.Write( msg.Data    );
            
            try
            {
                netStreamWriter.Flush();
                return true;
            }
            catch { return false; }
        }

        public bool SendNetMsg(NetServices service, string data)
        {
            NetMsg msg = new NetMsg(service, data);
            return SendNetMsg(msg);
        }

        private void HeartBeat()
        {   
            if (netStream.DataAvailable)// || stringBuffer != "")
            {
                tryRead    = client.Available;
                byteBuffer = new byte[tryRead];

                netStream.Read(byteBuffer, 0, tryRead);
                stringBuffer += Encoding.UTF8.GetString(byteBuffer);
            }
            if (stringBuffer != "")
            {
                try
                {
                    msgRecv      = ReadMsg(stringBuffer);
                    stringBuffer = stringBuffer.Remove(0, msgRecv.Length + 16);
                    NewNetMsg(msgRecv);
                }
                catch (NetMsgNotCompleteExeption e)
                {
                    Loger.LogLine(e.Message, Loger.ServerLogLevel.Verbose);
                }
                catch (NetMsgNotValidExeption e)
                {
                    Loger.LogLine(e.Message, Loger.ServerLogLevel.Verbose);
                    stringBuffer = "";
                }
            }
        }

        private void Listen()
        {
            while (true)
            {
                HeartBeat();
                System.Threading.Thread.Sleep(1);
            }
        }

        //TODO: rescrie ReadMsg si HeartBeat cu BinaryReader si/sau cu StringBuilder
        private NetMsg ReadMsg(string packet)
        {
            NetMsg ret = new NetMsg(NetServices.Blank);

            if (packet.Substring(0, 4) != NetConsants.PacketHeader)
            {
                int index = packet.IndexOf(NetConsants.PacketHeader);                
                if (index == -1) throw new NetMsgNotValidExeption("Message does not have a header, dumping message...");

                packet = packet.Remove(0, index);
            }

            packet = packet.Remove(0, 4);      //trim protocol GMSG

            ret.Version = packet.Substring(0, 4);
            packet = packet.Remove(0, 4);      //trim version 1000

            ret.Service = stringToNumber(packet.Substring(0, 4));
            packet = packet.Remove(0, 4);      //trim service

            int len = stringToNumber(packet.Substring(0, 4));
            packet = packet.Remove(0, 4);

            if (len > packet.Length) throw new NetMsgNotCompleteExeption("Message not complete, need to recive more...");

            ret.Data = packet.Substring(0, len);
            
            return ret;
        }


        #region Utils

        private int stringToNumber(string s)
        {
            int ret = 0;
            char[] chrs = s.ToCharArray();
            for (int i = 0; i < chrs.Length; i++)
            {
                ret = ret * 256 + (int)chrs[i];
            }
            return ret;
        }

        //private string writeInt(int number)
        //{
        //    byte a, b, c, d; // number = abcd
        //    string ret;

        //    d = (byte)(number % 256);
        //    number = number / 256;
        //    c = (byte)(number % 256);
        //    number = number / 256;
        //    b = (byte)(number % 256);
        //    number = number / 256;
        //    a = (byte)(number % 256);

        //    ret = new string(new char[] { (char)a, (char)b, (char)c, (char)d });

        //    return ret;
        //}

        #endregion

        #region Deprecated

        //private void writeInt(int number)
        //{
        //    byte a, b, c, d; // number = abcd

        //    d = (byte)(number % 256);
        //    number = number / 256;
        //    c = (byte)(number % 256);
        //    number = number / 256;
        //    b = (byte)(number % 256);
        //    number = number / 256;
        //    a = (byte)(number % 256);

        //    netStream.WriteByte(a);
        //    netStream.WriteByte(b);
        //    netStream.WriteByte(c);
        //    netStream.WriteByte(d);
        //}

        //private void writeString(string str)
        //{
        //    byte[] buf;
        //    buf = Encoding.ASCII.GetBytes(str);
        //    netStream.Write(buf, 0, str.Length);
        //}

        //private void FillStringBuffer()
        //{
        //    byte x;
        //    int k;
        //    k = netStream.ReadByte();
        //    buffer.Clear();
        //    while (k != -1)
        //    {
        //        x = (byte)k;
        //        buffer.Add(x);
        //        k = netStream.ReadByte();
        //    }

        //    byteBuffer = new byte[buffer.Count];
        //    for (int i = 0; i < buffer.Count; i++)
        //    {
        //        byteBuffer[i] = (byte)buffer[i];
        //    }
        //    byteBuffer = new byte[0];
        //    stringBuffer = stringBuffer + Encoding.ASCII.GetString(byteBuffer);
        //}
        #endregion
    }
}
